import { pushTarget, popTarget } from "../observe/dep";
import { queueWatcher } from "./schedular";

let id = 0;

// 干了三件事: 页面渲染, 计算属性， watch 监听
class Watcher {
  // 1. 渲染 watcher 时， options值为 true, exprOrFn为 function
  // 2. 用户 watcher 时， exprOrFn 为 字符串， options为对象，且有 user 属性
  constructor(vm, exprOrFn, cb, options) {
    this.vm = vm;
    this.cb = cb;
    this.exprOrFn = exprOrFn;
    this.options = options;
    this.user = options.user; // 表示是用户 watcher ，
    this.lazy = options.lazy; // 计算属性专用
    this.dirty = this.lazy; // 取值时是否执行用户提供的 computed 方法
    this.id = id++;
    this.depId = new Set(); // 用来去重，防止多次取同一数据时存入多个相同dep
    this.deps = [];
    this.getter = exprOrFn;

    // watch 中 value 直接就是函数

    // 渲染 watcher 中 exprOrFn 必定是 function
    // 用户 watcher 中 exprOrFn为字符串，就会到 vm 中取属性值，得到 newValue 和 oldValue
    if (typeof exprOrFn === "function") {
      this.getter = exprOrFn;
    } else {
      this.getter = function () {
        let path = exprOrFn.split("."); // key 是多层字符串时，需要分解  a.b.c 形式
        let obj = vm;
        for (let i = 0; i < path.length; i++) {
          obj = obj[path[i]]; // vm.a.b.c
        }
        return obj;
      };
    }

    // 默认会取值一次
    this.value = this.lazy ? void 0 : this.get(); // 调用了 render 方法；此时会对模板中的数据进行取值，即调用data 中数据的 get 方法；
  }

  get() {
    // 当调用此方法取值时，需要记住当前的 watcher 实例(方法时存到全局变量 Dep.target 上), 后面数据变化了就再次执行 此 watcher 实例
    pushTarget(this); // 取值之前，收集 watcher

    // 在计算属性  this.fullName 取值时， getter 即 fullName 函数, 它的 this 是watcher实例，因此要绑定 this，否则无法取到
    let result = this.getter.call(this.vm); // 会调用 vm._render , with 中会按模板顺序对data 中的属性取值
    popTarget(); // 取值完成后，将 watcher 删除
    return result;
  }

  // 同一个 watcher 中的 dep 不会重复
  addDep(dep) {
    let id = dep.id;
    // let idx = this.deps.indexOf(dep);

    // ============与下面一段等价 dep形成了闭包， 没有变量存放，但是被 deps 所引用=====================
    // if (idx < 0) {
    //   this.deps.push(dep);
    //   dep.addSub(this);
    // }

    // ============使用 id 来和 dep 关联，dep 也是闭包=====================
    let has = this.depId.has(id);
    if (!has) {
      this.depId.add(id);
      this.deps.push(dep);
      dep.addSub(this);
    }
  }
  run() {
    let newValue = this.get();
    let oldValue = this.value;
    this.value = newValue; // 更新
    if (this.user) {
      this.cb.call(this.vm, newValue, oldValue);
    }
  }

  // 更新页面
  update() {
    if (this.lazy) {
      // lazy 属性用来判断 是否是计算属性
      this.dirty = true;
    } else {
      // this.get(); // 每次修改数据都渲染更新
      queueWatcher(this); // 将 watcher 存放起来，后续异步批量更新
    }
  }

  // 计算属性专用
  evaluate() {
    this.value = this.get(); // 取值，调用 计算属性的方法
    this.dirty = false; // 表示已经取过一次值了
  }

  // watcher 收集所有依赖
  depend() {
    // 计算属性 watcher 会存储 dep ，反之 dep 会收集 watcher
    // 通过watcher 找到对应的所有dep,让所有 dep 记住此渲染 watcher
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }
}

export default Watcher;
